// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
#include <linux/rculist.h>
#include <net/tnl_expand.h>

MODULE_LICENSE("GPL");

/* return: 0 - handled success by hook; 1 - continue processed by kernel. */
static int (*g_gre_rcv_record_hook)(struct sk_buff *skb, __be16 flags,
				    u32 seqno, __be16 tnl_flags);
static void (*g_gre_pre_xmit_hook)(struct sk_buff *skb, struct ip_tunnel *tunnel,
				   struct iphdr *tnl_params, __be16 proto);
static void (*g_gre6_pre_xmit_hook)(struct sk_buff *skb, struct ip6_tnl *tunnel,
				    __be16 proto, __u8 *dsfield);
static void (*g_gre_xmit_time_refresh_hook)(struct sk_buff *skb);
/* return: 0 - accelerate successfully; 1 - accelerate failed; 3 - continue to kernel. */
static unsigned int (*g_gre_xmit_accel_hook)(struct sk_buff *skb, struct net_device *dev);
static void (*g_gre_set_fragoff_hook)(struct iphdr *tnl_params);
static void (*g_gre_tap_set_tcpmss_hook)(struct sk_buff *skb, struct net_device *dev,
					 struct ethhdr *ethh, uint32_t af_type);

void tnl_gre_set_hook(enum tnl_gre_hook type, void *hook)
{
	int i;
	void *no_use_ptr;

	struct tnl_hook_map hmap[TNL_GRE_HOOK_MAX] = {
		{ TNL_GRE_HOOK_NONE, "error", &no_use_ptr }, /* against mistake, not use !! */
		{ TNL_GRE_RCV_RECORD_HOOK, "rcv record", (void **)&g_gre_rcv_record_hook },
		{ TNL_GRE_PRE_XMIT_IP_HOOK, "pre xmit 4", (void **)&g_gre_pre_xmit_hook },
		{ TNL_GRE_PRE_XMIT_IP6_HOOK, "pre xmit 6", (void **)&g_gre6_pre_xmit_hook },
		{ TNL_GRE_XMIT_REFRESH_HOOK, "xmit time", (void **)&g_gre_xmit_time_refresh_hook },
		{ TNL_GRE_XMIT_ACCEL_HOOK, "xmit accel", (void **)&g_gre_xmit_accel_hook },
	};

	pr_warn("set gre hook: %d\n", type);
	for (i = 1; i < TNL_GRE_HOOK_MAX; i++) {
		if (hmap[i].type == type) {
			rcu_assign_pointer(*hmap[i].func, hook);
			pr_warn("set gre %s hook %s\n", hmap[i].desc, hook ? "on" : "null");
		}
	}
}
EXPORT_SYMBOL(tnl_gre_set_hook);

void tnl_gre_set_fragoff_hook(typeof(g_gre_set_fragoff_hook) hook)
{
	rcu_assign_pointer(g_gre_set_fragoff_hook, hook);
}
EXPORT_SYMBOL(tnl_gre_set_fragoff_hook);

void tnl_gre_tap_set_tcpmss_hook(typeof(g_gre_tap_set_tcpmss_hook) hook)
{
	rcu_assign_pointer(g_gre_tap_set_tcpmss_hook, hook);
}
EXPORT_SYMBOL(tnl_gre_tap_set_tcpmss_hook);

bool tnl_gre_rcv_seq_hook_valid(void)
{
	return g_gre_rcv_record_hook ? true : false;
}
EXPORT_SYMBOL(tnl_gre_rcv_seq_hook_valid);

bool tnl_gre_rcv_seq_handle(struct sk_buff *skb, __be16 flags, u32 seqno, __be16 tnl_flags)
{
	return (g_gre_rcv_record_hook && !g_gre_rcv_record_hook(skb, flags, seqno, tnl_flags));
}
EXPORT_SYMBOL(tnl_gre_rcv_seq_handle);

bool tnl_gre_xmit_header_build(struct sk_buff *skb, struct ip_tunnel *tunnel,
			       const struct iphdr *tnl_params, __be16 proto)
{
	if (g_gre_pre_xmit_hook) {
		g_gre_pre_xmit_hook(skb, tunnel, (struct iphdr *)tnl_params, proto);
		return true;
	}
	return false;
}
EXPORT_SYMBOL(tnl_gre_xmit_header_build);

bool tnl_gre6_xmit_header_build(struct sk_buff *skb, struct ip6_tnl *tunnel,
				__be16 proto, __u8 *dsfield)
{
	if (g_gre6_pre_xmit_hook) {
		g_gre6_pre_xmit_hook(skb, tunnel, proto, dsfield);
		return true;
	}
	return false;
}
EXPORT_SYMBOL(tnl_gre6_xmit_header_build);

bool tnl_gre_xmit_pkt_handle(struct sk_buff *skb, struct net_device *dev)
{
	if (g_gre_xmit_time_refresh_hook)
		g_gre_xmit_time_refresh_hook(skb);

	return (g_gre_xmit_accel_hook && !g_gre_xmit_accel_hook(skb, dev));
}
EXPORT_SYMBOL(tnl_gre_xmit_pkt_handle);

bool tnl_gre6_xmit_pkt_handle(struct sk_buff *skb, struct net_device *dev)
{
	if (g_gre_xmit_time_refresh_hook)
		g_gre_xmit_time_refresh_hook(skb);

	if (skb->protocol != htons(ETH_P_IP) &&
	    skb->protocol != htons(ETH_P_IPV6))
		return false;

	return (g_gre_xmit_accel_hook && !g_gre_xmit_accel_hook(skb, dev));
}
EXPORT_SYMBOL(tnl_gre6_xmit_pkt_handle);

void tnl_gre_pmtu_disc_handle(struct iphdr *tnl_params)
{
	if (g_gre_set_fragoff_hook)
		g_gre_set_fragoff_hook(tnl_params);
}
EXPORT_SYMBOL(tnl_gre_pmtu_disc_handle);

void tnl_gre_tap_set_tcpmss(struct sk_buff *skb, struct net_device *dev,
			    __be16 proto, bool is_rcv, uint32_t af_type)
{
	struct ethhdr *ethh;

	if (dev->type == ARPHRD_ETHER && proto == htons(ETH_P_TEB)) {
		ethh = (struct ethhdr *)(is_rcv ? skb->data : skb_inner_mac_header(skb));
		if (g_gre_tap_set_tcpmss_hook)
			g_gre_tap_set_tcpmss_hook(skb, dev, ethh, af_type);
	}
}
EXPORT_SYMBOL(tnl_gre_tap_set_tcpmss);

static int __init tnl_modules_init(void)
{
	pr_info("tunnel modules init for ont hooks.");
	return 0;
}

static void __exit tnl_modules_exit(void)
{
	pr_info("tunnel modules exit for ont hooks.");
}

module_init(tnl_modules_init);
module_exit(tnl_modules_exit);
